#ifndef ZPP_SENSOR_HPP
#define ZPP_SENSOR_HPP

#include "zpp/result.hpp"
#include <zephyr/drivers/sensor.h>
#include <zpp/assert.hpp>
#include <zpp/device.hpp>
namespace zpp {

#define ZPP_SENSOR_GET_BY_NODE(node_id)                                        \
  ZPP_DEVICE_GET_BY_NODE(zpp::sensor::sensor_base<ZPP_DT_NODE(node_id)>,       \
                         node_id)

#define ZPP_SENSOR_DEFINE(sensor_name, val_name, chan)                         \
  using sensor_name =                                                          \
      zpp::sensor::internal::sensor_base<zpp::sensor::internal::value<chan>>;

namespace sensor {

namespace internal {

template <sensor_channel k_chan> class value : protected sensor_value {
public:
  value() = default;
  constexpr value(int32_t v1, int32_t v2) : sensor_value(v1, v2) {}
  value(float v) { ZPP_ASSERT_EQ(sensor_value_from_float(this, v), 0); }
  value(double v) { ZPP_ASSERT_EQ(sensor_value_from_double(this, v), 0); }
  value(sensor_value& base) {
    this->val1 = base.val1;
    this->val2 = base.val2;
  }
  auto operator=(const value& other) -> value& {
    this->val1 = other.val1;
    this->val2 = other.val2;
    return *this;
  }
  auto operator-(const value& other) -> value& {
    this->val1 = this->val1 - other.val1;
    this->val2 = this->val2 - other.val2;
    return *this;
  }
  auto to_float() const { return sensor_value_to_float(this); }
  auto to_double() const { return sensor_value_to_double(this); }
  auto to_milli() const { return sensor_value_to_milli(this); }
  auto to_micro() const { return sensor_value_to_micro(this); }
  consteval static auto get_channel() { return k_chan; }
};

template <typename t_value>
  requires std::is_base_of_v<sensor_value, t_value>
class sensor_base {
public:
  using value_type = t_value;
  ZPP_SINGLETON(sensor_base);
  static auto &get_instance() {
    static sensor_base<t_value> ins;
    return ins;
  }
  constexpr sensor_base(const device *dev) : k_dev(dev){};
  auto sample_fetch() const -> zpp::error {
    if (k_dev != nullptr) [[likely]] {
      return zpp::error{sensor_sample_fetch(k_dev)};
    } else {
      return zpp::error_code::k_nodev;
    }
  }
  auto get_value() const -> zpp::result<t_value> {
    if (k_dev != nullptr) [[likely]] {
      sensor_value value;
      const auto err =
          zpp::error{sensor_channel_get(k_dev, t_value::get_channel(), &value)};
      if (not err) {
        return err;
      }
      return t_value{value};
    } else {
      return zpp::error{zpp::error_code::k_nodev};
    }
  }
  auto is_valid() const -> bool {
    return (k_dev != nullptr);
  }
protected:
  const device *k_dev;
};
} // namespace internal
ZPP_SENSOR_DEFINE(rotation, degree, SENSOR_CHAN_ROTATION);
} // namespace sensor

} // namespace zpp

#endif